package org.intellij.vcs.mks;

import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import org.intellij.vcs.mks.model.MksServerInfo;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author Thibaut Fagart
 */
public class MksConfigurableForm implements Configurable {
	private JPanel mainPanel;
	private JTable MKSSICommandLineTable;
	private JTextArea ignoredServersTA;
	private JTextField datePatternString;
	private JCheckBox resyncNonMembers;
	private DefaultTableModel tableModel;
	private MksConfiguration configuration;
	private JComboBox charsetEditorCombo = new JComboBox();
	private static final String DEFAULT_ENCODING_VALUE = "<DEFAULT>";

	public MksConfigurableForm(@NotNull final MksConfiguration configuration) {
		this.configuration = configuration;
		this.resyncNonMembers.setSelected(configuration.isSynchronizeNonMembers());
		initSupportedCharsets();
		reset();
	}

	private void initIgnoredServers() {
		final String ignoredServers = configuration.getIgnoredServers();
		ignoredServersTA.setText(ignoredServers);
	}

	private void initCommands() {
		setTableModel(createTableModel());
	}

	private void initSupportedCharsets() {
		final Set<String> stringSet = Charset.availableCharsets().keySet();
		final String[] charSetNames = stringSet.toArray(new String[stringSet.size()]);
		Arrays.sort(charSetNames);
		final String[] charSetNamesWithDefault = new String[charSetNames.length + 1];
		charSetNamesWithDefault[0] = MksConfigurableForm.DEFAULT_ENCODING_VALUE;
		System.arraycopy(charSetNames, 0, charSetNamesWithDefault, 1, charSetNames.length);
		charsetEditorCombo.setModel(new DefaultComboBoxModel(charSetNamesWithDefault));
	}

	private void setTableModel(@NotNull final DefaultTableModel model) {
		this.tableModel = model;
		MKSSICommandLineTable.setModel(this.tableModel);
		MKSSICommandLineTable.getColumn("Encoding").setCellEditor(new DefaultCellEditor(this.charsetEditorCombo));
	}

	@NotNull
	@Nls
	private DefaultTableModel createTableModel() {

		@Nls final String commandColumnName = "Command";
		@Nls final String EncodingCommandName = "Encoding";
		return new DefaultTableModel(createTableData(getConfiguration()),
				new String[]{commandColumnName, EncodingCommandName}) {
			@Override
			public boolean isCellEditable(final int row, final int column) {
				return column == 1;
			}
		};
	}

	private Object[][] createTableData(final MksConfiguration configuration) {
		final String[] knownCommands = MksVcs.getCommands();

		final Object[][] result = new Object[knownCommands.length + 1][];
		result[0] = new String[]{"Default", configuration.defaultEncoding};
		int i = 1;
		for (final String command : knownCommands) {
			final String commandEncoding = configuration.SI_ENCODINGS.getMap().get(command);
			result[i++] = new String[]{command,
					(commandEncoding == null) ? MksConfigurableForm.DEFAULT_ENCODING_VALUE : commandEncoding};
		}
		return result;
	}

	@Nls
	public String getDisplayName() {
		return "MKS";
	}

	@Nullable
	@NonNls
	public String getHelpTopic() {
		return null;
	}

	@Nullable
	public Icon getIcon() {
		return null;
	}

	public void apply() throws ConfigurationException {
		final MksConfiguration configuration = getConfiguration();
//		configuration.PROJECT = myFldProject.getText();
		configuration.SI_ENCODINGS.setMap(getEncodingMap());
		configuration.defaultEncoding = getDefaultEncoding();
		configuration.setSynchronizeNonMembers(this.resyncNonMembers.isSelected());
		try {
			configuration.setDatePattern(validateDatePattern());
		} catch (Exception e) {
			throw new ConfigurationException(
					"Bad date pattern " + this.datePatternString.getText() + ", must be a valid" +
							" java dateFormat pattern");

		}
		final List<MksServerInfo> ignoredServersListOld = parseIgnoredServers(configuration.getIgnoredServers());
		final List<MksServerInfo> ignoredServersListNew;
		try {
			ignoredServersListNew = parseIgnoredServers(ignoredServersTA.getText());
		} catch (IllegalArgumentException e) {
			throw new ConfigurationException(e.getMessage());
		}

		for (final MksServerInfo serverInfo : ignoredServersListOld) {
			if (!ignoredServersListNew.contains(serverInfo)) {
				configuration.serverIsSiServer(serverInfo, true);
			}
		}
		for (final MksServerInfo serverInfo : ignoredServersListNew) {
			if (!ignoredServersListOld.contains(serverInfo)) {
				configuration.serverIsSiServer(serverInfo, false);
			}
		}
	}

	private String validateDatePattern() {
		DateFormat format = new SimpleDateFormat(this.datePatternString.getText());
		format.format(new Date());
		return this.datePatternString.getText();
	}

	private List<MksServerInfo> parseIgnoredServers(final String serverList) {
		final StringTokenizer tok = new StringTokenizer(serverList, ",", false);
		final ArrayList<MksServerInfo> ret = new ArrayList<MksServerInfo>();
		while (tok.hasMoreTokens()) {
			final StringTokenizer tok2 = new StringTokenizer(tok.nextToken(), ":");
			if (tok2.countTokens() != 2) {
				throw new IllegalArgumentException(
						"bad server list, it has to be a comma separated list of <host:port>, example \"myServer1:7001,myServer2:7001\"");
			}
			ret.add(new MksServerInfo("anon", tok2.nextToken(), tok2.nextToken()));
		}
		return ret;

	}

	private String getDefaultEncoding() {
		return (String) tableModel.getValueAt(0, 1);
	}

	private Map<String, String> getEncodingMap() {
		final Map<String, String> result = new HashMap<String, String>();
		for (int row = 1, max = tableModel.getRowCount(); row < max; row++) {
			final String command = (String) tableModel.getValueAt(row, 0);
			final String encoding = (String) tableModel.getValueAt(row, 1);
			if (!encoding.equals(MksConfigurableForm.DEFAULT_ENCODING_VALUE)) {
				result.put(command, encoding);
			} else {
				// ignoring as it is the default value
			}
		}
		return result;
	}

	public void disposeUIResources() {
	}

	public JComponent createComponent() {
		return this.mainPanel;
	}

	public final void reset() {
		initCommands();
		initIgnoredServers();
		initDatePattern();
	}

	private void initDatePattern() {
		this.datePatternString.setText(configuration.getDatePattern());
	}

	private MksConfiguration getConfiguration() {
		return this.configuration;
	}

	public boolean isModified() {
		final MksConfiguration configuration = getConfiguration();
		boolean isDateChanged;
		try {
			isDateChanged = !validateDatePattern().equals(configuration.getDatePattern());
		} catch (Exception e) {
			isDateChanged = true;
		}
		return isResyncNonMembersModified(configuration)
				|| isEncodingsModified(configuration)
				|| (!configuration.getIgnoredServers().equals(ignoredServersTA.getText()))
				|| isDateChanged
//			&& configuration.PROJECT.equals(.getText())
				;
	}

	private boolean isResyncNonMembersModified(MksConfiguration configuration) {
		return configuration.isSynchronizeNonMembers() && this.resyncNonMembers.isSelected();
	}

	private boolean isEncodingsModified(final MksConfiguration configuration) {
		return true;
	}

	public static void main(final String[] args) {
		final JFrame frame = new JFrame("MksConfigurableForm");
		final MksConfiguration config = new MksConfiguration();
		config.serverIsSiServer(new MksServerInfo("anon", "ignoredServer", "7001"), false);
		frame.setContentPane(new MksConfigurableForm(config).mainPanel);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.pack();
		frame.setVisible(true);
	}


}
